home *** CD-ROM | disk | FTP | other *** search
-
- /*
- * (a) (C) 1990 by Adobe Systems Incorporated. All rights reserved.
- *
- * (b) If this Sample Code is distributed as part of the Display PostScript
- * System Software Development Kit from Adobe Systems Incorporated,
- * then this copy is designated as Development Software and its use is
- * subject to the terms of the License Agreement attached to such Kit.
- *
- * (c) If this Sample Code is distributed independently, then the following
- * terms apply:
- *
- * (d) This file may be freely copied and redistributed as long as:
- * 1) Parts (a), (d), (e) and (f) continue to be included in the file,
- * 2) If the file has been modified in any way, a notice of such
- * modification is conspicuously indicated.
- *
- * (e) PostScript, Display PostScript, and Adobe are registered trademarks of
- * Adobe Systems Incorporated.
- *
- * (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
- * CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
- * AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
- * ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
- * OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
- * WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
- * WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
- * DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
- * OF THIRD PARTY RIGHTS.
- */
-
- /*
- * DrawingView.m
- *
- * This view represents the page that the bezier is drawn onto. It is
- * a subview of the doc view and can be zoomed and reduced.
- *
- * The buffers are used to allow for fast redrawing and moving.
- * BuffeAlpha is a view inside a ClipView in a plain window and
- * bufferBeta is a view in a plain window without a ClipView. The
- * drawing is performed in the alpha buffer and then this is
- * composited into the buffered window. When the bezier is
- * selected and changed, the drawing of the new bezier
- * occurs in the buffered window. With each mouse drag the
- * old image in alpha is first composited into the window and
- * then the new bezier drawn atop the old image.
- *
- * When the image is moved, it is first drawn into the beta
- * buffer and then simply composited to a new location. If
- * the image is larger than the beta buffer, then the image
- * is redrawn instead of using the buffer.
- *
- * Version: 2.0
- * Author: Ken Fromm
- * History:
- * 03-07-91 Added this comment.
- */
-
- #import "Bezier.h"
- #import "HitPointView.h"
- #import "DetectApp.h"
- #import "DocView.h"
- #import "DrawingView.h"
- #import "DrawingViewWraps.h"
- #import <appkit/Application.h>
- #import <appkit/Cell.h>
- #import <appkit/Cursor.h>
- #import <appkit/Matrix.h>
- #import <appkit/View.h>
- #import <appkit/ClipView.h>
- #import <appkit/Window.h>
- #import <appkit/nextstd.h>
- #import <dpsclient/wraps.h>
- #import <appkit/timer.h>
-
- @implementation DrawingView
-
- static char fontname[ ] = "ControlPointsFont";
-
- /*
- * Timers used to automatically scroll when the mouse is
- * outside the drawing view and not moving.
- */
- static void startTimer(NXTrackingTimer ** timer, int *timermask, id window)
- {
- if (!*timer)
- {
- *timer = NXBeginTimer(NULL, 0.15, 0.2);
- *timermask = NX_TIMERMASK;
- [window addToEventMask:NX_TIMERMASK];
- }
- }
-
- static void stopTimer(NXTrackingTimer **timer, int *timermask, id window)
- {
- if (*timer)
- {
- NXEndTimer(*timer);
- *timer = NULL;
- *timermask = 0;
- [window removeFromEventMask:NX_TIMERMASK];
- }
- }
-
- static void compositeBuffer(int gstate, const NXRect * srce, const NXPoint * dest, int op)
- {
- PScomposite(NX_X(srce), NX_Y(srce), NX_WIDTH(srce), NX_HEIGHT(srce),
- gstate, dest->x, dest->y, op);
- }
-
- /*
- * Instance variables - hitPoint holds the usre path for
- * hit detection while drawUpath is a scratch buffer used for
- * several misc. purposes.
- */
- - initFrame:(NXRect *) frm
- {
- [super initFrame:frm];
- [[self allocateGState] setClipping:NO];
-
- [self createGrid];
- PSWDefineFont(fontname);
-
- /*
- * Normally these buffers might be allocated in the Application subclass and kept
- * as an application wide resource for use with multiple documents. It is allocated
- * here to simplify the code.
- */
- NX_MALLOC(drawUpath.pts, float, PTS_UPATH_BUFFER);
- NX_MALLOC(drawUpath.ops, char, OPS_UPATH_BUFFER);
-
- NX_MALLOC(hitPoint.pts, float, NUM_POINTS_HIT);
- NX_MALLOC(hitPoint.ops, char, NUM_OPS_HIT);
- [self initializeHitPoint];
-
- bufferAlpha = [NXApp getBufferAlpha];
- bufferBeta = [NXApp getBufferBeta];
-
- selected = NO;
- timer = NULL;
-
- return self;
- }
-
- /*
- * The user path uses relative movements to reduce the number
- * of points that have to be inserted each mouse down.
- */
- - initializeHitPoint
- {
- int i;
-
- for (i = 0; i < NUM_POINTS_HIT; i++)
- hitPoint.pts[i] = 0;
- hitPoint.num_pts = i;
-
- hitPoint.ops[0] = dps_setbbox;
- hitPoint.ops[1] = dps_moveto;
- hitPoint.ops[2] = dps_rlineto;
- hitPoint.ops[3] = dps_rlineto;
- hitPoint.ops[4] = dps_rlineto;
- hitPoint.ops[5] = dps_closepath;
- hitPoint.num_ops = 6;
-
- return self;
- }
-
- /*
- * Create a cached user path and store it in the server. The grid does not change
- * so it can be stored in the server instead of being resent each time. Drawing the
- * entire grid each time instead of just the portion that is necessary is faster in
- * this case because the cache is every time. When selectively drawing only
- * the portion necessary, a different path is cached thereby reducing the number
- * of cache hits.
- *
- */
- - createGrid
- {
- int i, j, num_pts, num_ops;
-
- float pt;
-
- char *ops;
-
- float *pts;
-
- num_ops = ceil((bounds.size.width/SIZEGRID + bounds.size.height/SIZEGRID) * 2) + 2;
- num_pts = num_ops * 2 + 4;
- NX_MALLOC(pts, float, num_pts);
- NX_MALLOC(ops, char, num_ops);
-
- i = j = 0;
- ops[j++] = dps_ucache;
-
- pts[i++] = bounds.origin.x;
- pts[i++] = bounds.origin.y;
- pts[i++] = bounds.origin.x + bounds.size.width;
- pts[i++] = bounds.origin.y + bounds.size.height;
- ops[j++] = dps_setbbox;
-
- for (pt = bounds.origin.x; pt < bounds.origin.x + bounds.size.width; pt += SIZEGRID)
- {
- pts[i++] = pt;
- pts[i++] = bounds.origin.y;
- ops[j++] = dps_moveto;
-
- pts[i++] = pt;
- pts[i++] = bounds.origin.y + bounds.size.height;
- ops[j++] = dps_lineto;
- }
-
- for (pt = bounds.origin.y; pt < bounds.origin.y + bounds.size.height; pt += SIZEGRID)
- {
- pts[i++] = bounds.origin.x;
- pts[i++] = pt;
- ops[j++] = dps_moveto;
-
- pts[i++] = bounds.origin.x + bounds.size.width;
- pts[i++] = pt;
- ops[j++] = dps_lineto;
- }
-
- /* Store it as a user object first by placing it on the stack and then defining it */
- PSWSetUpath(pts, i, ops, j);
- gridUpath = DPSDefineUserObject(gridUpath);
-
- if (pts)
- NX_FREE(pts);
- if (ops)
- NX_FREE(ops);
-
- return self;
- }
-
- /* When the bezier is created, make sure it can be seen. */
- - createObject
- {
- NXRect visRect;
-
- [self getVisibleRect:&visRect];
- objectId = [[Bezier alloc] initFrame:&visRect];
-
- return self;
- }
-
- - free
- {
- if (drawUpath.pts)
- NX_FREE(drawUpath.pts);
- if (drawUpath.ops)
- NX_FREE(drawUpath.ops);
-
- if (hitPoint.pts)
- NX_FREE(hitPoint.pts);
- if (hitPoint.ops)
- NX_FREE(hitPoint.ops);
-
- return [super free];
- }
-
-
- /*
- * Change the menu title and toggle the trace boolean
- */
- - traceDetect:sender
- {
- if (!tracedetect)
- [[sender selectedCell] setTitle:"Hit Detection On"];
- else
- [[sender selectedCell] setTitle:"Hit Detection Off"];
-
- tracedetect = !tracedetect;
-
- return self;
- }
-
- - traceDraw:sender
- {
- if (!tracedraw)
- [[sender selectedCell] setTitle:"Drawing On"];
- else
- [[sender selectedCell] setTitle:"Drawing Off"];
-
- tracedraw = !tracedraw;
-
- return self;
- }
-
- - drawGrid:sender
- {
- drawgrid = [sender state];
-
- [self display];
-
- return self;
- }
-
- - compositeBufferAlpha:(NXRect *) r
- {
- NXRect viewFrame;
-
- if (!r)
- {
- [self getVisibleRect:&viewFrame];
- r = &viewFrame;
- }
-
- [bufferAlpha lockFocus];
- compositeBuffer([self gState], r, &r->origin, NX_COPY);
- [bufferAlpha unlockFocus];
-
- return self;
- }
-
- /*
- * When the drawing view moves, then move bufferAlpha so that
- * the composites from bufferAlpha are taken from the correct spot.
- * The beta buffer does not need to be moved because the drawing
- * always starts in the lower left corner of the beta buffer.
- */
- - moveTo:(NXCoord)x :(NXCoord)y
- {
- [super moveTo:x :y];
- [bufferAlpha moveTo:x :y];
-
- return self;
- }
-
- /*
- * A scale that happens to this view should be reflected in the alpha and
- * beta buffers.
- */
- - scale:(NXCoord)x :(NXCoord)y
- {
- [super scale:x :y];
- [bufferAlpha scale:x :y];
- [bufferBeta scale:x :y];
-
- return self;
- }
-
- /*
- * A sizeTo should also be reflected in the alpha and beta buffers.
- */
- - sizeTo:(NXCoord)x :(NXCoord)y
- {
- [super sizeTo:x :y];
- [bufferAlpha sizeTo:x :y];
- [bufferBeta sizeTo:x :y];
-
- return self;
- }
-
- /*
- * Constrain the point within the view. An offset is needed because when
- * an object is moved, it is often grabbed in the center of the object. If the
- * lower left offset and the upper right offset were not included then part of
- * the object could be moved off of the view. (In some applications, that might
- * be allowed but in this one the object is constrained to always lie in the
- * page.)
- */
- - constrainPoint:(NXPoint *)aPt withOffset:(const NXSize*)llOffset :(const NXSize*)urOffset
- {
- float margin;
-
- NXPoint viewMin, viewMax;
-
- margin = ceil(FONTSIZE/2);
-
- viewMin.x = bounds.origin.x + llOffset->width + margin;
- viewMin.y = bounds.origin.y + llOffset->height + margin;
-
- viewMax.x = bounds.origin.x + bounds.size.width - urOffset->width - margin;
- viewMax.y = bounds.origin.y + bounds.size.height - urOffset->height - margin;
-
- aPt->x = MAX(viewMin.x, aPt->x);
- aPt->y = MAX(viewMin.y, aPt->y);
-
- aPt->x = MIN(viewMax.x, aPt->x);
- aPt->y = MIN(viewMax.y, aPt->y);
-
- return self;
- }
-
- /*
- * Constrain a rectangle within the view.
- */
- - constrainRect:(NXRect *)aRect
- {
- float margin;
-
- NXPoint viewMin, viewMax;
-
- margin = ceil(FONTSIZE/2);
-
- viewMin.x = bounds.origin.x + margin;
- viewMin.y = bounds.origin.y + margin;
-
- viewMax.x = bounds.origin.x + bounds.size.width - aRect->size.width - margin;
- viewMax.y = bounds.origin.y + bounds.size.height - aRect->size.height - margin;
-
- aRect->origin.x = MAX(viewMin.x, aRect->origin.x);
- aRect->origin.y = MAX(viewMin.y, aRect->origin.y);
-
- aRect->origin.x = MIN(viewMax.x, aRect->origin.x );
- aRect->origin.y = MIN(viewMax.y, aRect->origin.y);
-
- return self;
- }
-
- - (BOOL) isScrolling
- {
- return scrolling;
- }
-
- /*
- * Scrolls to rectangle passed in if it is not in visible portion of the view.
- * If the rectangle is larger in width or height than the view, the scrollRectToVisible
- * method is not altogether consistent. As a result, the rectangle contains only
- * the image that was previously visible.
- */
- - scrollToRect:(const NXRect *)toRect
- {
- NXRect visRect, tooRect;
-
- [self getVisibleRect:&visRect];
- if (!NXContainsRect(&visRect, toRect))
- {
- scrolling = YES;
- [window disableFlushWindow];
- [self scrollRectToVisible:toRect];
- [window reenableFlushWindow];
- scrolling = NO;
-
- startTimer(&timer, &timermask, window);
- }
- else
- stopTimer(&timer, &timermask, window);
-
- return self;
- }
-
- /*
- * Redraws the graphic. The image from the alpha buffer is composited
- * into the window and then the changed object is drawn atop the
- * old image. A copy of the image is necessary because when the
- * window is scrolled the alpha buffer is also scrolled. When the
- * alpha buffer is scrolled, the old image might have to be redrawn.
- * As a result, a copy is created and the changes performed on the
- * copy. Care is taken to limit the amount of area that must be
- * composited and redrawn. A timer is started is the scrolling rect
- * moves outside the visible portion of the view.
- */
-
- - redrawObject:(int) pt_num :(NXRect *)redrawRect
- {
- id copyId;
-
- BOOL tracking = YES;
-
- int old_mask;
-
- NXPoint pt, pt_last, pt_old, delta;
-
- NXRect rect_now, rect_last, rect_scroll, rect_vis;
-
- NXEvent *event;
-
- /*
- * Composite the current image in the window into the first buffer.
- */
- [self compositeBufferAlpha:NULL];
-
- /*
- * Create a copy of the selected object. If we scroll we will need
- * to redraw the old curve. If we do not create a copy we will
- * not have an old curve.
- */
- copyId = [objectId copy];
- [copyId copyPts:objectId];
-
- timermask = 0;
-
- if (tracedraw)
- DPSTraceContext(DPSGetCurrentContext(), YES);
-
- /*
- * The rect_scroll will cause scrolling whenever it goes outside the
- * visible portion of the view.
- */
- [self getVisibleRect:&rect_vis];
- [copyId getScrollRect:pt_num :&rect_scroll];
- NXIntersectionRect(&rect_vis, &rect_scroll);
-
- [copyId getBounds:&rect_now withKnobs:YES];
- *redrawRect = rect_last = rect_now;
-
- [copyId getPoint:pt_num :&pt_last];
- pt_old = pt_last;
-
- old_mask = [window addToEventMask:
- NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
- event = [NXApp getNextEvent:
- NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
- if (event->type != NX_MOUSEUP)
- {
- while (tracking)
- {
- /*
- * If its a timer event than use the last point. It will be converted to
- * into the view's coordinate so it will appear as a new point.
- */
- if (event->type == NX_TIMER)
- pt = pt_old;
- else
- pt = pt_old = event->location;
-
- [self convertPoint:&pt fromView:nil];
- [copyId constrainPoint:&pt andNumber:pt_num toView:self];
-
- delta.x = pt.x - pt_last.x;
- delta.y = pt.y - pt_last.y;
-
- if (delta.x || delta.y)
- {
- /* Change the point location and get the new bounds. */
- [copyId setPoint:pt_num :&delta];
- [copyId getBounds:&rect_now withKnobs:YES];
-
- /* Change the scrolling rectangle. */
- NXOffsetRect(&rect_scroll, delta.x, delta.y);
- [self scrollToRect:&rect_scroll];
-
- /* Composite the old image and then redraw the new curve. */
- compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
- [self drawObject:NULL for:copyId withUcache:NO];
- [self drawControl:NULL for:copyId];
-
- /* Flush the drawing so that it's consistent. */
- [window flushWindow];
- NXPing();
-
- rect_last = rect_now;
- pt_last = pt;
- }
- else
- stopTimer(&timer, &timermask, window);
-
- event = [NXApp getNextEvent:NX_MOUSEUPMASK|
- NX_MOUSEDRAGGEDMASK|timermask];
- tracking = (event->type != NX_MOUSEUP);
- }
- stopTimer(&timer, &timermask, window);
- }
- [window setEventMask:old_mask];
-
- [objectId free];
- objectId = copyId;
-
- /*
- * Figure out the area that has to be redrawn
- * (the union of the old and the new rectangles).
- */
- NXUnionRect(&rect_now, redrawRect);
-
- return self;
- }
-
- /*
- * Moves the graphic object. If the selected graphic can fit in the beta
- * buffer than the image is drawn into this buffer and then composited
- * to each new location. The image is redrawn at the new location
- * when the user releases the mouse button. If the selected graphic
- * cannot fit in the beta buffer than it is redrawn each time. This can
- * happen when the drawing view is scaled upwards.
- *
- * The offsets constrain the selected object to stay within the dimensions
- * of the view.
- */
- - moveObject:(NXEvent *)event :(NXRect *)redrawRect
- {
- BOOL tracking = YES, beta;
-
- int old_mask;
-
- float scale;
-
- NXSize llOffset, urOffset;
-
- NXPoint pt, pt_last, pt_old, delta, delta_scroll;
-
- NXRect rect_now, rect_start, rect_last, rect_scroll, rect_vis;
-
- /*
- * Composite the current image in the window
- * into the first buffer.
- */
- [self compositeBufferAlpha:NULL];
-
- if (tracedraw)
- DPSTraceContext(DPSGetCurrentContext(), YES);
-
- /* Check whether the object can fit in the second buffer. */
- [[bufferBeta superview] getFrame:&rect_now];
- [objectId getBounds:&rect_start withKnobs:YES];
- scale = [superview scale];
- if (rect_now.size.width > rect_start.size.width * scale &&
- rect_now.size.height > rect_start.size.height * scale)
- {
- [bufferBeta setDrawOrigin:rect_start.origin.x :rect_start.origin.y];
- [bufferBeta lockFocus];
- PSsetgray(NX_WHITE);
- PSsetalpha(0.0);
- NXRectFill(&rect_start);
- PSsetalpha(1.0);
- [self drawObject:NULL for:objectId withUcache:NO];
- [self drawControl:NULL for:objectId];
- [bufferBeta unlockFocus];
- beta = YES;
- }
- else
- beta = NO;
-
- /*
- * Get the scrolling rectangle. If it turns out to be the visible portion of the window
- * then reduce it a bit so that the user is not playing pong when trying to
- * move the image.
- */
- [objectId getScrollRect:-1 :&rect_scroll];
- [self getVisibleRect:&rect_vis];
- if (NXContainsRect(&rect_scroll, &rect_vis))
- {
- rect_scroll = rect_vis;
- NXInsetRect(&rect_scroll, rect_scroll.size.width * .20, rect_scroll.size.height * .20);
- }
- else
- NXIntersectionRect(&rect_vis, &rect_scroll);
-
- *redrawRect = rect_now = rect_last = rect_start;
- delta_scroll.x = rect_scroll.origin.x - rect_now.origin.x;
- delta_scroll.y = rect_scroll.origin.y - rect_now.origin.y;
-
- timermask = 0;
- pt_last = pt_old = event->location;
- [self convertPoint:&pt_last fromView:nil];
-
- /* Calculate where the mouse point falls relative to the object. */
- llOffset.width = pt_last.x - rect_start.origin.x;
- llOffset.height = pt_last.y - rect_start.origin.y;
- urOffset.width = rect_start.origin.x + rect_start.size.width - pt_last.x;
- urOffset.height = rect_start.origin.y + rect_start.size.height - pt_last.y;
-
- old_mask = [window addToEventMask:
- NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
- event = [NXApp getNextEvent:
- NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK];
- if (event->type != NX_MOUSEUP)
- {
- while (tracking)
- {
- if (event->type == NX_TIMER)
- pt = pt_old;
- else
- pt = pt_old = event->location;
-
- [self convertPoint:&pt fromView:nil];
- [self constrainPoint:&pt withOffset:&llOffset :&urOffset];
- [self constrainPoint:&pt_last withOffset:&llOffset :&urOffset];
- delta.x = pt.x - pt_last.x;
- delta.y = pt.y - pt_last.y;
-
- if (delta.x || delta.y)
- {
- NXOffsetRect(&rect_now, delta.x, delta.y);
- [self constrainRect:&rect_now];
-
- rect_scroll.origin.x = rect_now.origin.x + delta_scroll.x;
- rect_scroll.origin.y = rect_now.origin.y + delta_scroll.y;
- [self scrollToRect:&rect_scroll];
-
- /*
- * Copy the old image into the window. If using the second buffer, copy
- * it atop the old buffer. Otherwise, translate and redraw.
- */
- compositeBuffer([bufferAlpha gState], &rect_last, &rect_last.origin, NX_COPY);
- if (beta)
- {
- compositeBuffer([bufferBeta gState], &rect_start, &rect_now.origin,
- NX_SOVER);
- }
- else
- {
- PSgsave();
- PStranslate(rect_now.origin.x - rect_start.origin.x,
- rect_now.origin.y - rect_start.origin.y);
- [self drawObject:NULL for:objectId withUcache:YES];
- [self drawControl:NULL for:objectId];
- PSgrestore();
- }
-
- /* Flush the drawing so that it's consistent. */
- [window flushWindow];
- NXPing();
-
- rect_last = rect_now;
- pt_last = pt;
- }
- else
- stopTimer(&timer, &timermask, window);
-
- event = [NXApp getNextEvent:NX_MOUSEUPMASK|
- NX_MOUSEDRAGGEDMASK|timermask];
-
- tracking = (event->type != NX_MOUSEUP);
- }
- stopTimer(&timer, &timermask, window);
-
- delta.x = rect_now.origin.x - rect_start.origin.x;
- delta.y = rect_now.origin.y - rect_start.origin.y;
- [objectId moveAll:&delta];
- }
-
- NXUnionRect(&rect_now, redrawRect);
- [window setEventMask:old_mask];
-
- return self;
- }
-
- /* Check to see whether a control point has been hit. */
- - (BOOL) checkControl:(const NXPoint *) p :(int *) pt_num
- {
- BOOL hit;
-
- float controlsize, hitsetting;
-
- NXRect hitRect;
-
- controlsize = [superview controlPointSize];
- hitsetting = [superview hitSetting];
- NXSetRect(&hitRect, p->x - hitsetting/2, p->y - hitsetting/2, hitsetting, hitsetting);
- hit = [objectId hitControl:&hitRect :pt_num :controlsize];
-
- return hit;
-
- }
-
- /*
- * A method in the DrawingView class. Invoked by the mouse down
- * event. The mouse point and the current hit setting dimensions
- * are placed in the hit point user path before invoking the
- * hitObject method of the Bezier object.
- */
- - (BOOL) checkObject:(const NXPoint *) p
- {
- BOOL hit;
-
- float hitsetting;
-
- hitsetting = [superview hitSetting];
-
- /* Bounding Box */
- hitPoint.pts[0] = floor(p->x - hitsetting/2);
- hitPoint.pts[1] = floor(p->y - hitsetting/2);
- hitPoint.pts[2] = ceil(p->x + hitsetting/2);
- hitPoint.pts[3] = ceil(p->y + hitsetting/2);
-
- /* Moveto */
- hitPoint.pts[4] = p->x - hitsetting/2;
- hitPoint.pts[5] = p->y - hitsetting/2;
-
- /* Rlineto's */
- hitPoint.pts[7] = hitsetting;
- hitPoint.pts[8] = hitsetting;
- hitPoint.pts[11] = -hitsetting;
-
- if (tracedetect)
- DPSTraceContext(DPSGetCurrentContext(), YES);
-
- hit = [objectId hitObject:&hitPoint];
-
- if (tracedetect)
- DPSTraceContext(DPSGetCurrentContext(), NO);
-
- return hit;
-
- }
-
- /*
- * If the docview is zooming, then scale the drawing view. Else
- * check for hit detection on the bezier or the control points.
- */
- - mouseDown:(NXEvent *)event
- {
- BOOL redraw = YES;
-
- int pt_num;
-
- NXPoint p;
-
- NXRect drawRect;
-
- p = event->location;
- if ([superview isZooming])
- return [nextResponder scaleDrawView:self toPoint:&p];
-
- [self convertPoint:&p fromView:nil];
- [self lockFocus];
- if (!selected)
- {
- if ([self checkObject:&p])
- {
- [objectId getBounds:&drawRect withKnobs:YES];
- selected = YES;
- }
- else
- redraw = NO;
- }
- else
- {
- if ([self checkControl:&p :&pt_num])
- [self redrawObject:pt_num :&drawRect];
- else if ([self checkObject:&p])
- [self moveObject:event :&drawRect];
- else
- {
- [objectId getBounds:&drawRect withKnobs:YES];
- selected = NO;
- }
- }
-
- /*
- * The view has already been focused and we know what
- * has to be redrawn so call drawSelf:: instead of display
- */
- if (redraw)
- {
- [self drawSelf:&drawRect :1];
- [window flushWindow];
- NXPing();
- }
-
- [self unlockFocus];
-
- return self;
- }
-
- /*
- * Draw the object. This is a relatively simple drawing operation. More
- * sophiticated drawing apps would probably want to pass the current
- * drawing parameters to avoid unnecessarily resetting them with
- * each drawing.
- */
- - drawObject:(NXRect *)r for:object withUcache:(BOOL)flag
- {
- [object drawObject:r withUcache:flag];
-
- return self;
- }
-
- /*
- * Draw the control points using the user path buffer to hold
- * the data for the xyshow. Having the object fill in the data
- * structure allows for combining the control points for multiple
- * objects. This increases drawing performance by reducing
- * the number of operations.
- */
- - drawControl:(NXRect *)r for:object
- {
- int i;
-
- NXPoint lastpoint;
-
- PSselectfont(fontname, [superview controlPointSize]);
- PSsetgray(NX_BLACK);
- PSsetlinewidth(0.15);
-
- lastpoint.x = 0;
- lastpoint.y = 0;
-
- drawUpath.num_ops = 0;
- drawUpath.num_pts = 0;
- [object putControlUPath:&drawUpath forRect:r :&lastpoint];
- if (drawUpath.num_ops > 0)
- {
- drawUpath.ops[drawUpath.num_ops] = 0;
- drawUpath.pts[drawUpath.num_pts] = 0;
- drawUpath.pts[drawUpath.num_pts + 1] = 0;
-
- PSWDrawControlPoints(drawUpath.pts[0], drawUpath.pts[1],
- &drawUpath.pts[2], drawUpath.num_pts, drawUpath.ops);
- }
-
- drawUpath.num_ops = 0;
- drawUpath.pts[0] = 99999;
- drawUpath.pts[1] = 99999;
- drawUpath.pts[2] = -99999;
- drawUpath.pts[3] = -99999;
- drawUpath.num_pts = 4;
- [object putControlLinesUPath:&drawUpath forRect:r];
- if (drawUpath.num_ops > 0)
- {
- DPSDoUserPath(&drawUpath.pts[4], drawUpath.num_pts - 4, dps_float,
- drawUpath.ops, drawUpath.num_ops, drawUpath.pts, dps_ustroke);
- }
-
- return self;
- }
-
- /*
- * Compare the bounds of the object with the rectangle to draw in order to
- * eliminate unnecessary drawing.
- */
- - drawSelf:(NXRect *)r :(int) count
- {
- NXRect objRect;
-
- if (tracedraw)
- DPSTraceContext(DPSGetCurrentContext(), YES);
-
- PSsetgray(NX_WHITE);
- NXRectFill(r);
-
- if (drawgrid)
- {
- PSgsave();
- PSsetgray(COLORGRID);
- PSsetlinewidth(WIDTHGRID);
- NXRectClip(r);
- PSWUpathStroke(gridUpath);
- PSgrestore();
- }
-
- [self drawObject:r for:objectId withUcache:YES];
- if (selected && NXDrawingStatus == NX_DRAWING)
- [self drawControl:r for:objectId];
-
- if (scrolling)
- [self compositeBufferAlpha:r];
-
- if (tracedraw)
- {
- if (![superview isTraceZooming] || ![superview isZooming])
- DPSTraceContext(DPSGetCurrentContext(), NO);
- }
-
- return self;
- }
-
- @end
-